package com.github.baichuan.iot.gateway.context;

import com.github.benmanes.caffeine.cache.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.time.Duration;
import java.util.concurrent.Executors;

/**
 * 基于caffeine实现的设备上下文缓存
 */
@Slf4j
public class CaffeineDeviceContextCache extends DeviceContextCache {
    private Cache<String, DeviceContext> cache;

    public CaffeineDeviceContextCache(DeviceContextSupplier contextSupplier,
                                      ThreadPoolTaskExecutor taskExecutor,
                                      Duration offline) {
        super(contextSupplier, taskExecutor);

        this.cache = Caffeine.newBuilder()
                .expireAfterAccess(offline)
                .scheduler(Scheduler.forScheduledExecutorService(Executors.newScheduledThreadPool(1)))
                .removalListener((RemovalListener<String, DeviceContext>) (device, context, cause) -> {
                    if(context.isDebug()){
                        log.info("Device {} Context is removed from cache, reason is {}", device, cause.name());
                    }

                    if(cause.wasEvicted() && context.isDebug()){
                        log.info("Tbox {} was evicted", device);
                    }

                    if(cause == RemovalCause.EXPIRED){
                        log.debug("device {} is expired offline", device);
                        cache.invalidate(device);
                        // 发布设备离线通知
                        offline(context);
                    }
                })
                .build();
    }

    @Override
    public DeviceContext get(String device) {
        return cache.get(device, t -> {
            DeviceContext context = contextSupplier.get(t);
            return context;
        });
    }

    @Override
    public boolean contains(String device) {
        return cache.getIfPresent(device) != null;
    }

    @Override
    public void evict(String device) {
        log.debug("evict device {} context from cache and close channel.", device);
        if(contains(device)){
            DeviceContext context = get(device);
            context.close();
            cache.invalidate(device);
        }
    }

    @Override
    public void refresh(String device) {
        if(contains(device)){
            DeviceContext context = get(device);
            contextSupplier.refresh(context);
        }
    }
}
